library(tidyverse)
library(lubridate)
library(rvest)
library(janitor)

Se importa la tabla “Compustat Global Daily” con los tipos de datos correctos.

global_daily <- read_csv("Compustat_Global_Daily.csv",
  col_types = cols(
    sedol = col_character(),
    datadate = col_date(format = "%Y%m%d")
  )
)

Extraemos de Wikipedia la tabla de códigos GICS con sus respectivos nombres.

gics_table <- read_html("https://en.wikipedia.org/wiki/Global_Industry_Classification_Standard") %>%
  html_node("table") %>%
  html_table(fill = TRUE) %>%
  as_tibble(.name_repair = "universal") %>%
  distinct(gsector = Sector...1, gics_name = Sector...2)

Se le añaden los nombres de los sectores a la tabla original

global_daily <- left_join(global_daily, gics_table, by = "gsector")

Se añade una variable para identicar de manera única a cada registro.

global_daily <- global_daily %>% 
  arrange(datadate) %>% 
  mutate(id = row_number())

Tipo de variable de cada columna

global_daily %>%
  summarise_all(class) %>%
  pivot_longer(everything(), names_to = "column", values_to = "type")

Exploratory analysis

Podemos notar que tenemos una tablas con 784102 filas y 21 columnas.

¿Cuántos valores distintos hay por cada columna? y ¿ Cuántos NA hay en cada columna?

global_daily %>% 
  summarise_all(n_distinct)
global_daily %>%
  summarise_all(~ sum(is.na(.x)))

Deberá llamarnos la atención que no hay correspondencia uno a uno entre conm y gvkey, por lo que suponemos que un conm tiene 2 gvkey, pues hay 292 gvkey y 291 conm Veamos…

global_daily %>% 
  select(gvkey, conm) %>% 
  group_by(conm) %>% 
  summarise(distinct_key = n_distinct(gvkey)) %>% 
  filter(distinct_key > 1)

Caso Nacional Financiera

Efectivamente es “NACIONAL FINANCIERA SNC” quien tiene asociados dos gvkey distintos. Veamos cuales son

global_daily %>% 
  filter(conm == "NACIONAL FINANCIERA SNC") %>% 
  group_by(gvkey) %>% 
  summarise(n = n(), prim_ap = min(datadate), ult_ap = max(datadate))

Lo que nos hace suponer que desde agosto de 2013 coexisten los dos gvkey, por lo que tendría que haber más de un registro por día

global_daily %>% 
  filter(conm == "NACIONAL FINANCIERA SNC") %>% 
  group_by(datadate) %>% 
  summarise(n = n()) %>% 
  filter(datadate >= "2013-08-07", datadate <= "2019-11-26")

Efectivamente cada día hay dos registros cada uno con diferente gvkey. Tomemos de muestra al 2019.

global_daily %>%
  filter(
    conm == "NACIONAL FINANCIERA SNC",
    datadate >= "2013-08-07",
    datadate <= "2019-11-26"
  ) %>%
  mutate(mv = cshoc * prccd) %>% #Creamos la mv para quedarnos con la de mayor valor
  group_by(datadate, conm) %>% 
  mutate(rank = rank(-mv)) # Creamos el ranking de mayor a menor 
# Veremos que los que tienen el que gvkey que aparece menos veces son también los 
# que los que tienen menor mv, así que más adelante los eliminaremos.

¡¿Qué hacemos?! ¿Quitamos los 1,645 registros de gvkey 315924? Respuesta: Hay que quitarlos…


Siguiendo con el análisis… Vemos que hay cinco diferentes monedas curcddy cinco diferentes mercados exchg. Donde el código 208 corresponde a la bolsa mexicana y que tiene 708,038 que coinciden con la suma de los registros con las monedas MXP, MXN y los NA. Así que todos los registros que no sean exchg = 208 son candidatos a eliminación

global_daily %>% 
  count(curcdd) 
global_daily %>% 
  count(exchg)
global_daily %>%
  count(curcdd) %>%
  filter(curcdd == "MXN" | curcdd == "MXP" | is.na(curcdd)) %>%
  summarise(not_eur_usd = sum(n))

observaciones de cada compañía

global_daily %>% 
  count(conm) %>% 
  arrange(n)
# Emisoras con solo una observación y en que fecha aparece
global_daily %>% 
  group_by(conm) %>%
  mutate(n = n()) %>%  
  filter(n == 1)

¿Cuántas empresas hay por sector?

# Temporalmente se usa el gsector... Que luego sabremos que en este caso es impreciso
# y tomaremos una categorización diferente
global_daily %>% 
  group_by(gics_name) %>% 
  summarise(n = n_distinct(conm))

Se comienza a limpiar

# Se eliminan los registros que no correspondan al código de mercado de la BMV, así
# como el registro restante en USD 

# CHECKPOINT
global_daily1 <- global_daily %>% 
  filter(exchg == 208, !curcdd %in% "USD")

# Solo quedan registros en MXP, MXN y NA
global_daily1 %>% 
  count(curcdd)
# Se procede a ver cuales son los registros que tiene NA en moneda, se selecciona
# el conjunto } fecha/emisora con el fin de ver si se tiene algún repetido que sí tenga moneda,
# para poder eliminar el que aparezca sin moneda
# 515 registros que no traen moneda
curcdd_na <- global_daily1 %>% 
  filter(is.na(curcdd)) %>% 
  mutate(dummy = paste(as.character(datadate), conm)) %>% 
  pull(dummy)

# Se selecciona los registros que sí tienen moneda y tienen un repetido sin moneda
# Se busca si alguno de los 515 registros que no tren moneda tiene algún registro de
# idéntica fecha y nombre pero que sí traiga moneda (un gemelo bueno)
rep_curr <- global_daily1 %>% 
  mutate(dummy = paste(as.character(datadate), conm)) %>% 
  filter(dummy %in% curcdd_na) %>% 
  filter(curcdd == "MXN") %>%  # 151 **istintos** conjuntos dadtadate/conm 
  pull(dummy)

# Se busca en toda la base esos 151 registros, para encontrar para esa fecha y
# compañía todos los registros (con y sin moneda) y se filtran los que no tienen moneda
filter_out <-  global_daily1 %>% 
  mutate(dummy = paste(as.character(datadate), conm)) %>% 
  filter(dummy %in% rep_curr & is.na(curcdd)) %>% 
  pull(id)

### CHECKPOINT
global_daily1 <- global_daily1 %>% 
  filter(!id %in% filter_out)
# ---

# ¿Cuantos NA en moneda quedan? Esos no los quitamos todavía pues no tienen un gemelo
# bueno
global_daily1 %>% 
  count(curcdd)
# Puesto que los que restan con NA en curcdd también tienen NA en varias columnas más
# procedo a eliminarlas
global_daily1 %>%
  summarise_all(~ sum(is.na(.x)))
## CHECKPOINT
global_daily1 <- global_daily1 %>% 
  filter(!is.na(curcdd))
# Como se ha progresado con repecto a los NA
global_daily1 %>%
  summarise_all(~ sum(is.na(.x)))

Todas las empresas que tienen alguna fecha duplicada

# Cuales son el combinado de fecha/empresa que aparecen en la base más de una vez
# y los extraemos hacia un vector
filt_dup <- global_daily1 %>% 
  group_by(datadate, conm) %>% 
  count(datadate) %>% 
  filter(n > 1)  %>% 
  mutate(dummy = paste(as.character(datadate),conm)) %>% 
  pull(dummy)

# Se crea un dataframe con los registros dupolicados
dups <- global_daily1 %>%
  mutate(dummy = paste(as.character(datadate), conm)) %>%
  filter(dummy %in% filt_dup)

dups

¿Cuántas empresas diferentes tienen duplicados y cuáles son?

length(unique(dups$conm))
## [1] 72
unique(dups$conm)
##  [1] "TUBOS DE ACERO DE MEXICO"     "TELMEX-TELEFONOS DE MEXICO"  
##  [3] "WAL MART DE MEXICO SA"        "INDUSTRIAS PENOLES SAB DE CV"
##  [5] "KIMBERLY-CLARK DE MEXICO SA"  "GRUPO MEXICO SAB DE CV"      
##  [7] "ALFA SAB DE CV"               "GRUPO BIMBO SA DE CV"        
##  [9] "CEMEX SAB DE CV"              "BCO NAC MEXICO"              
## [11] "GRUPO INDUSTRIAL MASECA"      "TELEINDUSTRIA ERICCSON SA"   
## [13] "GRUPO FINANCIERO SERFIN SA"   "GRUPO KUO SAB DE CV"         
## [15] "GRUPO FINANCIERO BANAMEX ACC" "GRUPO TMM S.A.B"             
## [17] "SYNKRO SA DE CV"              "GRUPO SIDEK SA DE CV"        
## [19] "GRUPO TELEVISA SAB"           "GRUPO CONDUMEX SA DE CV"     
## [21] "GPO FINANCIERO BBVA BANCOMER" "GRUPO CARSO SA DE CV"        
## [23] "CONTROLADORA COMERCIAL MEX"   "INDUSTRIAS NACOBRE SA DE CV" 
## [25] "AEROVIAS DE MEXICO SA DE CV"  "RASSINI SAB DE CV"           
## [27] "APASCO SA DE CV"              "EL PUERTO DE LIVERPOOL SA"   
## [29] "ALUMSA ALUMINIO SA DE CV"     "GRUPO INDUSTRIAL SALTILLO"   
## [31] "GRUPO POSADAS SAB DE CV"      "INTL DE CERAMICA SA DE CV"   
## [33] "EMPAQUES PONDEROSA SA"        "GRUPO HERDEZ SAB DE CV"      
## [35] "ORGANIZACION CULTIBA SAB DE"  "GRUPO MEXICANO DESARROLLO"   
## [37] "PEPSI-GEMEX SA DE CV"         "ACCEL SAB DE CV"             
## [39] "CIA MINERA AUTLAN SA DE CV"   "GRUPO IUSACELL SA"           
## [41] "GPO FIN INVERMEXIC"           "SAN CRISTOBAL SA"            
## [43] "GRUPO FINANCIERO INBURSA SA"  "SISTEMA AXIS SA"             
## [45] "GRUPO FINANCIERO BANORTE SA"  "GRUPO FINANCIERO BANCRECER"  
## [47] "GRUPO ELEKTRA SA DE CV"       "CASA DE BOLSA FINAMEX SAB DE"
## [49] "GRUPO SANBORN SA DE CV"       "EDITORIAL DIANA SA DE CV"    
## [51] "GRUPO PALACIO DE HIERRO"      "ORBIA ADVANCE CORP SAB DE CV"
## [53] "VALORES MONTERREY AETNA SA"   "REGIO EMPRESAS SA DE CV"     
## [55] "MAQUINARIA DIESEL SA DE CV"   "GRUPO MACMA SA DE CV"        
## [57] "SOCIEDAD ELECTROMECANICA SA"  "GRUPO RADIO CENTRO SA DE CV" 
## [59] "FOMENTO ECONOMICO MEXICANO"   "GRUPO ICONSA SA DE CV"       
## [61] "INTERAMERICANA ENTRTENMIENTO" "AMERICA MOVIL SA DE CV"      
## [63] "CONSORCIO G GRUPO DINA SA"    "GENERAL DE SEGUROS SAB"      
## [65] "HYLSAMEX SA DE CV"            "CYDSA SA"                    
## [67] "DINE S.A.B. DE C.V."          "TELMEX INTERNACIONAL SAB DE" 
## [69] "BIO PAPPEL SAB DE CV"         "NACIONAL FINANCIERA SNC"     
## [71] "PROMOTORA Y OPERADORA INFRAE" "TELESITES SAB DE CV"

Para la limpieza de duplicados tomaremos el registro con mayor mv = cshoc * prccd Pero antes de proceder a ello tenemos que considerar los registros en que cshoc tiene NA

# Identificamos y extraemos a un vector la combinacion de fecha y empresa que tienen
# NA en cshoc para poder ver si tienen un duplicado que sí tenga un valor en cshoc
cshoc_na <- dups %>% 
  filter(is.na(cshoc)) %>% 
  pull(dummy)

dups %>% 
  filter(dummy %in% cshoc_na) 
# Se ve que todos los registros tienen alguno igual pero sin na en cshoc, entonces se
# pueden eliminar los que tienen na
# Todos tienen al menos un gemelo bueno, entonces podemos eliminar al gemelo malo
dups %>% 
  filter(dummy %in% cshoc_na) %>%  
  group_by(datadate, conm) %>% 
  summarise(sin_na = sum(!is.na(cshoc)), con_na = sum(is.na(cshoc))) 
elim2 <- dups %>% 
  filter(dummy %in% cshoc_na, is.na(cshoc)) %>% 
  pull(id)


## CHECKPOINT 
global_daily1 <- global_daily1 %>% 
  filter(!id %in% elim2)

# Se actualiza el datafrane de duplicados
dups <- dups %>% 
  filter(!id %in% elim2)

# Ya noy hay na en cshoc en el dataframe de duplicados
dups %>% 
  summarise_all(~ sum(is.na(.x)))
# Creamos la nueva variable con la cual decidiremos con que registro quedarnos de los
# que estén duplicados
dups <- dups %>% 
  mutate(mv = cshoc * prccd)

dups
dups %>% 
  group_by(datadate, conm) %>% 
  count() 
dups %>% 
  group_by(conm) %>% 
  count() 
dups <- dups %>% 
  group_by(datadate, conm) %>% 
  mutate(rank = rank(-mv)) # %>% 
  #filter(rank > 1.5)
dups
elim3 <- dups %>% 
  filter(rank > 1.5) %>% 
  pull(id)

#### Checkpoint

global_daily1 <- global_daily1 %>% 
  filter(!id %in% elim3)
#####

#Los duplicados que quedan. Actualizamos dups
dups <- dups %>% 
  filter(rank == 1.5)  # será fácil quitar los que no tengan isin
dups
dups %>% 
  count(datadate,conm)
dups %>% 
 summarise(na = sum(is.na(isin)), not_na = sum(!is.na(isin)))
elim4 <- dups %>% 
  filter(is.na(isin)) %>% 
  pull(id)

#### Checkpoint
global_daily1 <- global_daily1 %>% 
  filter(!id %in% elim4)
####


#Actualiza dups

vec_dups <- dups %>% 
 summarise(na = sum(is.na(isin)), not_na = sum(!is.na(isin))) %>% 
  filter(na == 0) %>% 
  mutate(dummy = paste(as.character(datadate), conm)) %>% 
  pull(dummy)

dups <- dups %>% 
  filter(dummy %in% vec_dups)

dups
# De estas dos empresas que quedan en duplicados, vemos que una de las variables por las que 
# se diferencian es el isin, así que eliminaremos los registros que tengan el isin con menos
# repeticines en toda la base
global_daily %>%
  filter(conm == "GRUPO ELEKTRA SA DE CV" | conm == "CYDSA SA") %>%
  group_by(conm, isin) %>%
  count()
dups %>%
  group_by(conm, isin) %>%
  count()
isin_elim <- global_daily %>%
  filter(conm == "GRUPO ELEKTRA SA DE CV" | conm == "CYDSA SA") %>%
  group_by(conm, isin) %>%
  count() %>% 
  ungroup() %>% 
  top_n(-2, n) %>% 
  pull(isin)

## CHECKPOINT
elim5 <- dups %>% 
  filter(isin %in% isin_elim) %>% 
  pull(id)

global_daily1 <- global_daily1 %>% 
  filter(!id %in% elim5)
####

# Ya no existen duplicados de una misma empresa con fechas repetidas en la base
dups <- get_dupes(global_daily1, datadate, conm)
## No duplicate combinations found of: datadate, conm
global_daily1 %>% 
  count(conm) %>% 
  arrange(n)
global_daily1 %>% 
  mutate(year = year(datadate)) %>% 
  group_by(conm, year) %>% 
  summarise(n = n()) %>% 
  spread(year, n)
global_daily1 %>%
  summarise_all(~ sum(is.na(.x)))
## CHECKPOINT
# Se eliminaran por este momento aquellas empresas que tengan menos de 60 registros

conm_men60 <- global_daily1 %>% 
  count(conm) %>% 
  arrange(n) %>% 
  filter(n < 60) %>% 
  pull(conm)

elim6 <- global_daily1 %>% 
  filter(conm %in% conm_men60) %>% 
  pull(id)

## CHECKPOINT
global_daily1 <- global_daily1 %>% 
  filter(!id %in% elim6)

Veamos la cuestión de los sectores

global_daily1 %>% 
  distinct(conm, gics_name)

global_daily1 %>% mutate(year = year(datadate)) %>% group_by(conm, year) %>% summarise(n = sum(monthend)) %>% spread(year, n)

global_daily1 %>% 
  filter(monthend == 1) %>% 
  select(datadate, conm, monthend) %>% 
  mutate(dia = day(datadate)) %>% 
  filter(dia >= 5 & dia < 25)
global_daily1 %>%
  mutate(mes = month(datadate), year = year(datadate)) %>%
  filter(
    conm == "INDUSTRIAS NACOBRE SA DE CV",
    year == 1988 | year == 1987
    #mes == 1 | mes == 2
  ) %>% 
  select(datadate, conm, monthend)
global_daily1 %>% 
  mutate(year = year(datadate)) %>% 
  group_by(gics_name, year) %>%
  summarise(n = n_distinct(conm)) %>% 
  spread(year, n)

Siguiente paso: crear la variable de los percios

Esta es otra prueba. Es para recordar lo que ya hicimos.

A comer ya ¿no?

Prueba 3. A comer.

Prueba 4. Rober quiere borrar sólo una línea. Esta es la segunda línea. Y esta es la tercera.

Prueba 5. Una tercera fila de datos.